Автоматизация задач администрирования через LDAP-запросы¶
Введение¶
Автоматизация позволяет ускорить выполнение массовых операций администрирования, например, если требуется завести тысячу пользователей, следует написать скрипт для импорта учетных записей из CSV-файла, автоматически распределяющий пользователей по подразделениям и в соответствующие группы. Этот способ позволяет экономить время и исключает ошибки, связанные с человеческим фактором.
В данной инструкции описано устройство LDAP-каталога, алгоритм управления доменом с помощью прямых запросов к каталогу, а также несколько полезных запросов для решения реальных задач администрирования.
Технология LDAP¶
Служба каталога ALD Pro построена на базе 389 Directory Server, который реализует функции каталога и поддерживает протокол LDAPv3. Облегченный протокол доступа к данным каталога (Lightweight Directory Access Protocol, LDAP) является упрощенной модификацией более строгих стандартов для построения службы распределенного каталога сети X.500.
Каталог LDAP является специализированной нереляционной базой данных, файлы которой расположены в папке /var/lib/dirsrv/slapd-ald-company-lan/db. Информация каталога представлена в виде древовидной структуры, которую также называют Directory Information Tree или сокращенно DIT. См. Структура каталога Directory Information Tree
Рисунок 51 Структура каталога Directory Information Tree¶
Корень каталога называется Root DSE, где DSE означает Directory system agent Specific Entry, то есть специализированная запись агента системы директорий. Эта запись не имеет родителя, и она описана в файле /etc/dirsrv/slapd-ALD-COMPANY-LAN/dse.ldif, где slapd-ALD-COMPANY-LAN - директория сервиса slapd c именем домена.
Корень Root DSE является родителем для записей dc=ald,dc=company,dc=lan и cn=changelog, которые так же называют базовыми записями, корневыми суффиксами или контекстами именования (base entry, root suffix, naming context). В контексте dc=ald,dc=company,dc=lan хранятся все объекты каталога, а в cn=changelog – журнал изменений для работы плагина Retro Changelog Plugin. Все контексты, определенные в каталоге, указаны в операционном атрибуте namingContexts записи Root DSE, однако спецификация LDAP позволяет также использовать служебные контексты, например, в записи cn=config представлены настройки каталога, а через запись cn=monitor можно получить доступ к информации о состоянии сервера в режиме реального времени.
Существуют разные подходы в задании наименований корневых суффиксов, в ALD Pro (FreeIPA) используются правила спецификации RFC-2247, поэтому для домена ald.company.lan имя корневого суффикса будет dc=ald,dc=company,dc=lan, где dc – компонент имени домена, сокращение от Domain Component. Правила наименования необходимы для возможности преобразования DNS-имени в имя LDAP-записи и наоборот.
От корневого суффикса dc=ald,dc=company,dc=lan ответвляются дочерние записи (Entry), которые, в свою очередь, могут быть контейнерами для других записей, за счет чего и образуется древовидная структура. Таким образом, записи каталога можно сравнить с директориями файловой системы.
У каждой записи каталога есть имя, которое должно быть уникальным в пределах родительского контейнера, поэтому оно называется относительно уникальным именем Relative Distinguished Name или кратко RDN. Учетные записи пользователей, например, имеют имена uid=admin, uid=ivan.kuznetsov и т.д. Особенность имен объектов в LDAP заключается в том, что они хранят не конкретные значения, а только ссылки на хранимые атрибуты записей, которые используются для идентификации объектов. Например, для идентификации учетных записей пользователей используют атрибут uid, для учетных записей компьютеров FQDN (fully qualified domain name, полное доменное имя хоста), а в именах контейнеров обычно присутствует cn (common name, общее имя).
В приведенном примере RDN учетной записи доменного администратора uid=admin состоит из названия атрибута uid, после которого идет символ присвоения = и далее значение атрибута admin. Вся эта запись вместе называется «Определением Значения Атрибута» - Attribute Value Assertion (AVA). Обычно имена записей задаются значением одного атрибута, но могут использоваться и несколько, тогда в имени RDN эти определения AVA будут объединяться знаком +, например: cn=Ivan+l=Moscow. Каталог 389 Directory Server поддерживает такой способ именования записей, но в ALD Pro (FreeIPA) он не используется.
Для идентификации объекта в пределах всего каталога используют уникальное имя Distinguished Name или сокращенно DN. Уникальное имя представляет из себя цепочку RDN, которые записывают через запятую слева направо, начиная с целевой записи и до корневого суффикса вверх по иерархии, см. Пример формирования уникального имени записи DN. Если привести аналогию с объектами файловой системы, то RDN будет соответствовать имени объекта директории или файла, а DN – полному имени объекта файловой системы, которое включает путь к родительскому объекту и имени объекта. И также как на одном диске не может быть двух директорий с одинаковыми полными именами, в каталоге LDAP не может быть двух записей с одинаковыми DN. Описание формата DN можно найти RFC 4514.
Рисунок 52 Пример формирования уникального имени записи DN¶
Запись каталога может хранить не только дочерние записи, но и набор атрибутов, описывающих ее свойства. Например, для учетной записи пользователя это могут быть ФИО, должность, номер телефона и т.д.
Данные в LDAP-каталоге строго структурированы и все атрибуты должны быть определены в схеме данных заранее. Перечень доступных для конкретного объекта атрибутов задается списком назначенных ему классов, см. атрибут objectClass. Например, учетные записи пользователей могут содержать атрибут gidNumber по той причине, что им назначен класс объектов posixAccount, см. Возможность указать gidNumber определяется наличием класса объектов posixAccount.
Рисунок 53 Возможность указать gidNumber определяется наличием класса объектов posixAccount¶
Всем объектам каталога назначен, как минимум, один класс top, т.к. в нем описан атрибут objectClass, с помощью которого работает механизм назначения классов. Объекты с одним классом практически не используются, поэтому в каталоге у объектов два и более класса.
Для демонстрации ниже указан класс объектов posixGroup, который определен в схеме следующим образом:
( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Standard LDAP objectclass' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) X-ORIGIN 'RFC 2307' )
где:
1.3.6.1.1.1.2.2– это идентификатор объекта (object id,OID). Глобальные идентификаторы присваиваются международными организациями (IANA, ISO, ITU-T, ANSI, BSI), а для расширения схемы в прикладных системах используется пространство номеров с префиксом1.3.6.1.4.1.X, гдеX– это внутренний номер организации. Например, объекты компании X имеют префикс1.3.6.1.4.1.47836.;
NAME ' '– инструкция NAME задает имя класса;
DESC ' '– инструкция DESC задает описание класса;
SUP ' '– инструкция SUP указывает родительский класс. В приведенном примере наследование идет от классаtop, поэтому объектам будет доступен его атрибутobjectClass;
STRUCTURAL– инструкция указывает, что класс относится к виду структурных. Существуют также абстрактные (ABSTRACT) и вспомогательные (AUXILIARY) классы, но в контексте автоматизации различия между видами классов не принципиальны;
MUSTиMAY– инструкции, которые позволяют задать списки обязательных и дополнительных атрибутов. Полный перечень атрибутов, доступных объекту, расширяется атрибутами, которые наследуются от всех родительских классов;
X-ORIGIN ' '– инструкция, которая позволяет задать комментарий с ссылкой на документацию, из которой можно почерпнуть дополнительную информацию об этом классе. В приведенном примере информацию следует искать в документе RFC 2307.
Один и тот же атрибут может быть использован в нескольких классах, поэтому пользователям можно задать значение gidNumber, т.к. им назначен класс posixAccount, и, в то же время, он может быть задан у групп, т.к. им назначен класс posixGroup, см. Использование атрибута gidNumber классами posixAccount и posixGroup.
Рисунок 54 Использование атрибута gidNumber классами posixAccount и posixGroup¶
Подробное описание атрибута gidNumber:
( 1.3.6.1.1.1.1.1 NAME 'gidNumber' DESC 'Standard LDAP attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'RFC 2307' )
где:
1.3.6.1.1.1.1.1– это уникальный идентификатор атрибута;
NAME ' '– инструкция NAME задает атрибута;
DESC ' '– инструкция DESC задает описание атрибута;
SYNTAX ' '– инструкция задает тип хранимого в атрибуте значения. В приведенном примере1.3.6.1.4.1.1466.115.121.1.27соответствует целым числам. Описание всех типов данных можно найти в RFC4517;
SINGLE-VALUE– инструкция указывает, что атрибут может хранит только одно значение. Если этой инструкции не будет, то объектам можно будет присваивать несколько значений этого атрибута;
X-ORIGIN ' '– инструкция, которая позволяет задать комментарий с ссылкой на документацию.
Описание схемы данных хранится в файлах на диске в директориях:
/usr/share/dirsrv/schema/
/etc/dirsrv/schema/
/usr/share/dirsrv/updates/
/etc/dirsrv/slapd-ALD-COMPANY-LAN/
При обращении к каталогу по LDAP информацию можно получить из операционного DIT cn=schema.
Взаимодействие с каталогом через LDAP-протокол¶
Графическое приложение для работы с LDAP-каталогом (Apache Directory Studio)¶
Для работы с LDAP-каталогом из графического интерфейса: просмотра структуры каталога, редактирования записей, импорта и экспорта данных – можно воспользоваться таким бесплатным инструментом, как Apache Directory Studio, см. Apache Directory Studio. Загрузить приложение можно с официального сайта, для работы потребуется java runtime.
Рисунок 55 Apache Directory Studio¶
Настройка соединения¶
Чтобы подключиться к LDAP-каталогу нужно создать новое соединение через меню LDAP > New connection. Откроется окно для создания нового подключения см. Настройка сети для нового LDAP-подключения.
Рисунок 56 Настройка сети для нового LDAP-подключения¶
Примечание
По умолчанию Apache Directory Studio предлагает создать незащищенное подключение на порт 389, что допустимо только при обращении к каталогу по localhost, т.е. приложение должно быть установлено непосредственно на контроллер домена, т.к. при подключении пароль будет передаваться в открытом виде. В случае подключения к каталогу с клиента, обязательно использование порта 636 и метода шифрования SSL, чтобы перехват пароля был невозможен.
Аутентификация по паролю называется связыванием (Bind). Подключение к каталогу доступно с правами супер-пользователя cn=Directory Manager или доменного администратора ALD Pro uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan, см. Настройка аутентификации для нового LDAP-подключения. В зависимости от учетной записи права на доступ к записям и атрибутам разнятся.
Примечание
Сразу после установки системы пароли этих учетных записей совпадают. Чтобы сбросить пароль Directory Manager потребуется вручную менять хэш, записанный в файле dse.ldif, в строке, начинающейся с nsslapd-rootpw.
Перед закрытием окна следует проверить корректность подключения нажатием кнопки Check Authentication.
Рисунок 57 Настройка аутентификации для нового LDAP-подключения¶
После добавления подключения новый сервер появится в списке Connections. Для подключения к серверу нужно дважды кликнуть по новой записи.
Просмотр и экспорт объектов каталога¶
Для просмотра записи в древе каталога следует выбрать нужный RDN из окна LDAP browse. Например, при нажатии на пункт cn=accounts из списка слева, основные атрибуты отобразятся в центральном окне с именем cn=accounts,dc=ald,dc=company,dc=lan. См. Окно LDAP browse.
Рисунок 58 Окно LDAP browse¶
Также можно открыть любой DN из меню Navigate > Go to DN, например cn=config, но для его просмотра нужна учетная запись cn=Directory Manager, см. Полезные DN.
Запись DN |
Описание |
|---|---|
cn=accounts,dc=ald,dc=company,dc=lan |
Контейнер который содержит дочерние записи контейнеров учетных записей, компьютеров, групп и д.р. |
cn=computers,cn=accounts,… |
Содержит список компьютеров в домене |
cn=dns,… |
Содержит информацию о записях DNS |
cn=groups,cn=accounts,… |
Содержит список групп пользователей |
cn=hostgroups, cn=accounts,… |
Содержит группы компьютеров домена, например ipaservers |
cn=orgunits,cn=accounts,… |
Содержит список подразделений, которые отображаются у других записей в атрибуте rbtadp, например у пользователя или компьютера |
cn=users,cn=accounts,… |
Содержит список пользователей домена |
Экспорт объектов производится через контекстное меню, кликом по требуемой записи, например, cn=users в дереве см. Окно LDAP browse, а затем по Export с дальнейшим выбором формата.
Рисунок 59 Окно LDAP browse¶
После выбора формата CSV откроется диалог настроек параметров экспорта данных, см. Окно параметров CSV Export. В окне параметров настройте нужные фильтры и список требуемых атрибутов для вывода.
Рисунок 60 Окно параметров CSV Export¶
Следующим шагом укажите имя файла, в который вы хотите записать CSV, см. Окно CSV Export выбор имени файла, а затем нажмите Finish.
Рисунок 61 Окно CSV Export выбор имени файла¶
Если нажать на ссылку Text Formats см. Настройки вывода текстового форматирования, откроются настройки генерации текстовых данных, таких как обрамление данных кавычками, разделитель строк и др.
Рисунок 62 Настройки вывода текстового форматирования¶
Результат выполнения экспорта данных в программе LibreOffice Calc - см. Результат выполнения экспорта данных.
Рисунок 63 Результат выполнения экспорта данных¶
Этот файл можно использовать для автоматизации в качестве входной информации или для подготовки отчета по объектам из каталога LDAP.
Просмотр схемы каталога¶
Для просмотра схемы каталога следует выбрать Root DSE в дереве LDAP Browser и выполнить команду LDAP > Open Schema Browser см. Меню Open Schema Browser.
Рисунок 64 Меню Open Schema Browser¶
По умолчанию в Schema Browser открывается страница для просмотра классов объектов (Object Classes), подробной информации (Details) и поиска. Например, по фильтру posix выводятся упоминаемые ранее классы posixAcccount и posixGroup, см. Schema Browser и вкладка Object Classes.
Рисунок 65 Schema Browser и вкладка Object Classes¶
Для перехода к другим группам объектов предназначены ярлычки в нижней части вкладки. Страница Attribute Types предназначена для просмотра атрибутов, поиска по списку всех атрибутов и просмотра деталей выбранного атрибута. Например, поиск по фильтру gid выводит несколько атрибутов, в именах которых содержится эта подстрока, см. Schema Browser и вкладка Attribute Types.
Рисунок 66 Schema Browser и вкладка Attribute Types¶
Утилиты для работы с LDAP-каталогом¶
Для работы с LDAP-каталогом из командной строки используются утилиты пакета ldap-utils:
ldapwhoami- выполняет подключение к каталогу и возвращает имя текущего пользователя;ldapsearch- выполняет поиск по каталогу с указанными параметрами;ldapadd- позволяет создать новый объект в каталоге;ldapdelete- позволяет удалить объект из каталога;ldapmodify- позволяет изменить атрибуты существующего объекта в каталоге;ldapcompare- позволяет сравнить текущее значение атрибута конкретной записи с желаемым значением и получить результат в формате TRUE/FALSE;ldapexop- позволяет выполнять расширенные операции, добавленные разработчиками конкретного LDAP-сервера. Расширенные операции подобны хранимым процедурам SQL, и позволяют расширять возможности взаимодействия с сервером без внесения изменений в LDAP-протокол;ldappasswd- позволяет сбросить пароль для учетной записи;ldapmodrdn- позволяет переименовывать существующие объекты каталога.
Подключение к LDAP¶
Подключение к LDAP-каталогу происходит в рамках вызова каждой утилиты. Допустимо задавать параметры подключения в явном виде или полагаться на настройки по умолчанию, которые описаны в файле /etc/ldap/ldap.conf. При вводе компьютера в домен указанный файл настраивается автоматически, подключение будет выполняться к одному из контроллеров домена по протоколу LDAPS с использованием Kerberos-билета из связки ключей.
Проверка доступа в каталог осуществляется командой ldapwhoami:
ldapwhoami
Результат выполнения:
SASL/GSSAPI authentication started
ldap_sasl_interactive_bind_s: Local error (-2)
additional info: SASL(-1): generic failure: GSSAPI Error: Unspecified GSS failure. Minor code may provide more information (No Kerberos credentials available (default cache: KEYRING:persistent:1000))
Данный результат сообщает об отсутствии учетных записей в кэше Kerberos. Поэтому следует выполнить вход kinit admin и проверить доступ повторно ldapwhoami:
kinit admin
ldapwhoami
Результат выполнения:
Password for admin@ALDPRO.DOMAIN:
SASL/GSSAPI authentication started
SASL username: admin@ALDPRO.DOMAIN
SASL SSF: 256
SASL data security layer installed.
dn: uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
Наличие TGT-Kerberos билета позволяет получить сервисный билет на доступ к LDAP-серверу. Проверить текущие билеты в кэше можно командой klist:
klist
Результат выполнения:
Ticket cache: KEYRING:persistent:1000:1000
Default principal: admin@ALD.COMPANY.LAN
Valid starting Expires Service principal
22.05.2023 09:58:25 23.05.2023 09:58:01 ldap/dc-1.ald.company.lan@ALD.COMPANY.LAN
22.05.2023 09:58:08 23.05.2023 09:58:01 krbtgt/ALD.COMPANY.LAN@ALD.COMPANY.LAN
В кеше присутствует билет LDAP: ldap/dc-1.ald.company.lan@ALD.COMPANY.LAN, благодаря этому возможно использование утилиты из пакета ldap-utils.
При подключении к серверу можно переопределять любой из параметров по умолчанию, задавая их в явном виде:
ldapsearch -H dc-1.ald.company.lan -ZZ -x -W -D "uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan" -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" '(uid=*)' uid givenName sn
где:
Параметр
-H- для указания адреса LDAP-сервера, напримерdc-1.ald.company.lan. Параметр позволяет указать нешифрованный протокол ldap или шифрованный ldaps, например,ldap://dc-1.ald.company.lan. Подключение к LDAP-каталогу возможно также через unix-сокет, если приложение выполняется на том же сервере. В этом случае следует указатьldapi://%2fvar%2frun%2fslapd-ALD-COMPANY-LAN.socket.Параметр
-ZZ- это параметр включения шифрованного соединения, где первая Z означает отправку серверу запросаSTARTTLS. Если сервер не поддерживает TLS, соединение продолжится, и оно не будет шифроваться. Вторая Z устанавливает требование использовать только шифрованное соединение, если сервер не поддерживает TLS, соединение прервется. Рекомендуется использовать двойной ключ-ZZдля установления шифрованного соединения, если по каким-либо причинам 636-порт недоступен, а также требование использовать безопасный протокол TLS на 636-порту можно задать с помощью ключа-H, указав порт явноldaps://dc-1.ald.company.lan;Параметр
-x- указывает на необходимость выполнить простую аутентификацию по логину/паролю;Параметр
-D- задает Bind DN пользователя для аутентификации, например,uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lanили супер-пользователя,cn=Directory Manager;Параметр
-W- указывает, что пароль должен быть предоставлен в интерактивном режиме (а если нужно передать пароль в параметре, то это можно сделать с помощью ключа-w);
Используя параметры -D и -W, доступно подключение к нужному серверу по IP или имени сервера и файл конфигурации задействован не будет.
Поиск по каталогу ldapsearch¶
Для поиска информации можно использовать утилиту ldapsearch, которая извлекает из каталога набор записей с указанными атрибутами в соответствии с заданными критериями фильтрации и заданного списка атрибутов. Синтаксис команды в общем виде выглядит следующим образом:
ldapsearch [options] [filter [attributes…]]
где:
options- параметры вызова, в т.ч. параметры подключения. Для простоты использованы параметры подключения по умолчанию;filter- служит для точного указания критериев поиска;attributes- указывает, какие атрибуты необходимо запросить.
Далее приведен пример запроса, который должен выдавать список всех пользователей домена из cn=users, которая в свою очередь является потомком записи cn=accounts, и все они находятся в пространстве корневого суффикса dc=ald,dc=company,dc=lan. Таким образом полным уникальным именем записи (Distinguished Name, DN), в которой хранятся пользователи, является cn=users,cn=accounts,dc=ald,dc=company,dc=lan.
Поиск по каталогу с помощью команды ldapsearch:
ldapsearch -Q -s sub -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" '(uid=*)' uid givenName sn
где:
Параметр
-Q– это тихий режим SASL, не выводится информация о SASL-подключении, который полезен при автоматизации для очистки данных от технической информации.- Параметр
-s– это область поиска (scope). Может принимать следующие значения: one- поиск идет по дочерним записям на один уровень ниже в иерархии;sub- поиск идет по всем дочерним записям на всю глубину иерархии, параметр по умолчанию;children- то же, что иsub, но ограничивает поиск только дочерними записями;base- ограничивает поиск по текущей записи, заданной параметром-b. Если заданone, поиск идет по дочерним записям на один уровень ниже в иерархии. Если заданsub, то поиск идет по всем дочерним записям на всю глубину иерархии, начиная с записи, заданной параметром-b, при этом включая саму базовую запись.children- то же, что иsub, но ограничивает поиск только дочерними записями, не включая базовую запись.
- Параметр
Параметр
-b– это базовая запись (base), которая будет использоваться в качестве начальной точки для поиска по древу.Параметр
'(uid=\*)'– это фильтр, в котором осуществлен поиск всех записей, имеющих атрибутuid. Фильтры и составные фильтры рассматриваются далее.Параметры
uid givenName sn– это атрибуты, которые нужно вывести в результат. Если их не указывать, то будут отображены все атрибуты найденных записей.
Результат поиска по каталогу:
### extended LDIF
#
### LDAPv3
### base <cn=users,cn=accounts,dc=ald,dc=company,dc=lan> with scope subtree
### filter: (uid=*)
### requesting: uid givenName sn
#
### admin, users, accounts, ald.company.lan
dn: uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
uid: admin
sn: Administrator
### petrovp, users, accounts, ald.company.lan
dn: uid=petrovp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
uid: petrovp
givenName:: 0J/RkdGC0YA=
sn:: 0J/QtdGC0YDQvtCy
### ivani, users, accounts, ald.company.lan
dn: uid=ivani,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
uid: ivani
givenName:: 0JjQstCw0L0=
sn:: 0JjQstCw0L3QvtCy
### search result
search: 4
result: 0 Success
### numResponses: 4
### numEntries: 3
Результат выводится в поток stdout в текстовом формате LDIF и его можно использовать по конвейеру pipeline или перенаправить в файл для дальнейшей обработки. Подробнее о формате LDIF см. в разделе 2.2.3.
Существуют операционные атрибуты (operational attributes), которые встроены в LDAP-сервер и управляются им самим, например, entrydn, entryid, parentid или nsAccountLock. Большинство операционных атрибутов для пользователей доступны только для чтения. Чтобы их увидеть, необходимо указать “+” в конце команды, однако не все операционные атрибуты доступны для просмотра таким образом:
ldapsearch -Q -LLL -s base -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "+"
Результат выполнения:
dn: cn=users,cn=accounts,dc=ald,dc=company,dc=lan
creatorsName: cn=Directory Manager
modifiersName: uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
createTimestamp: 20230324160645Z
modifyTimestamp: 20230324161206Z
nsUniqueId: df91d884-ca5d11ed-b5f0ee72-e6acffe1
parentid: 2
entryid: 3
entryusn: 6055
numSubordinates: 6
Например, из результата видно, что запись cn=users имеет операционный атрибут numSubordinates, который показывает число дочерних записей.
Фильтры запроса¶
Поиск ldapsearch можно сделать более гибким с помощью фильтров, задав условие для отбора нужной информации. Синтаксис фильтра выглядит следующим образом:
(attribute operator value)
В таблице ниже приведены операторы (operator), используемые в фильтрах:
Оператор |
Комментарий |
|---|---|
|
Больше или равно. Возвращает результат, если атрибут больше или равен какому-то значению, например (uidNumber>=180003) |
|
Меньше или равно. Возвращает результат, если атрибут меньше или равен какому-то значению, например: (uidNumber<=180003) |
|
Наличие. Возвращает результат, если атрибут содержит одно или более значений, например: (cn=*) |
|
Равенство. Возвращает результат, если атрибут равен некоторому значению, например: (sn=petrov) |
|
Примерное равенство. Возвращает результат, если атрибут содержит приблизительно схожее значение, например: (cn~=petrof) |
|
Включает в себя. Возвращает значение, если атрибут содержит определенную подстроку. Где знак «*» означает ноль или более символов, например: (cn=pet*ov) |
Например, чтобы вывести список пользователей с фамилией petrov, потребуется указать фильтр по атрибуту sn (surname):
ldapsearch -Q -LLL -s one -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(sn=Петров)" cn
Результат выполнения:
dn: uid=ppetrov,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
cn: Petr Petrov
В результате запрос отфильтрован по атрибуту sn. Таким образом можно подготовить любые данные для обработки.
Составные фильтры запроса¶
Для большей гибкости можно использовать несколько фильтров, объединяя их с помощью логических операторов. Синтаксис составного фильтра выглядит следующим образом:
(Boolean-operator(filter)(filter)(filter)...)
Ниже приведены логические операторы (Boolean-operator), используемые в составных фильтрах:
& - Логическое И. Фильтр возвращает те записи, которые удовлетворяют всем указанным условиям, например:
(&(uid=user)(uidNumber=180003))| - Логическое ИЛИ. Фильтр возвращает те записи, которые удовлетворяют одному из указанных условий, например:
! - Логическое НЕ. Фильтр возвращает те записи, которые не удовлетворяют указанному условию, например:
(!(uid=admin))
Например, поиск пользователей из группы admins, которые два и более месяца не меняли пароль, выглядит так:
ldapsearch -Q -LLL -s one -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(&(memberof=cn=admins*)(krbLastPwdChange>=$(date +"%Y%m%d000000Z" -d "-60 days")))" cn krbLastPwdChange
Результат выполнения:
dn: uid=admin,cn=users,cn=accounts,
cn: Administrator
krbLastPwdChange: 20230324160900Z
В результате запроса, выведен список администраторов, у которых пароль изменялся за последние 60 дней. Через конструкцию можно добавлять подкоманды: $(date +“%Y%m%d000000Z” -d “-60 days”) между двойными кавычками фильтра. Так, например, можно вычислить дату и подставить ее в фильтр в нужном формате.
Получение схемы объектных классов¶
Объектные классы описаны в операционном атрибуте objectClasses из специальной записи схемы cn=schema. Посмотреть список всех классов можно командой:
ldapsearch -Q -LLL -o ldif-wrap=no -s base -b "cn=schema" objectClasses
Результат выполнения:
dn: cn=schema
objectClasses: ( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass X-ORIGIN 'RFC 45
12' )
objectClasses: ( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectNam
e X-ORIGIN 'RFC 4512' )
objectClasses: ( 2.5.20.1 NAME 'subschema' AUXILIARY MAY ( dITStructureRules $
nameForms $ dITContentRules $ objectClasses $ attributeTypes $ matchingRules
$ matchingRuleUse ) X-ORIGIN 'RFC 4512' )
objectClasses: ( 1.3.6.1.4.1.1466.101.120.111 NAME 'extensibleObject' SUP top
AUXILIARY X-ORIGIN 'RFC 4512' )
objectClasses: ( 2.5.6.11 NAME 'applicationProcess' SUP top STRUCTURAL MUST cn
MAY ( seeAlso $ ou $ l $ description ) X-ORIGIN 'RFC 4519' )
...
В результате выведется список всех классов, в котором можно искать через команду grep:
ldapsearch -Q -LLL -o ldif-wrap=no -s base -b "cn=schema" objectClasses | grep --color posix
Результат выполнения:
objectClasses: ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Standard LDAP objectclass' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) X-ORIGIN 'RFC 2307' )
objectClasses: ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Standard LDAP objectclass' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) X-ORIGIN 'RFC 2307' )
objectClasses: ( 2.16.840.1.113730.3.2.326 NAME 'dynamicGroup' DESC 'Group containing internal dynamically-generated members' SUP posixGroup AUXILIARY MAY dsOnlyMemberUid X-ORIGIN 'Red Hat Directory Server' )
Получение схемы атрибутов¶
Также, как и объектные классы, атрибуты описаны через операционный атрибут attributeTypes корневой записи схемы cn=schema. Посмотреть список всех атрибутов можно командой:
ldapsearch -Q -LLL -o ldif-wrap=no -s base -b "cn=schema" attributeTypes
Результат выполнения:
dn: cn=schema
attributeTypes: ( 2.16.840.1.113730.3.1.582 NAME 'nsDS5ReplicaCredentials' DES
C 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 SINGL
E-VALUE X-ORIGIN 'Netscape Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.8.22.1.2 NAME 'ipaCertMapMapRule' DESC '
Certificate Mapping Rule' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X
-ORIGIN 'IPA v4.5' )
attributeTypes: ( 1.3.6.1.4.1.15953.9.1.1 NAME 'sudoUser' DESC 'User(s) who ma
y run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SY
NTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'SUDO' )
...
Результат команды также можно перенаправить в файл командой перенаправления >:
ldapsearch -Q -LLL -o ldif-wrap=no -s base -b "cn=schema" attributeTypes > attrs
Формат данных LDIF¶
Как упоминалось ранее, утилита ldapsearch возвращает результаты в формате LDIF, LDAP Data Interchange Format – это формат представления записей службы каталогов или их изменений в текстовой форме. Записи каталога или их изменения представляются набором LDIF-записей, по одной на каждую запись каталога или изменение. LDIF был разработан в начале 90-х годов и был доработан для использования с LDAPv3. Описание формата опубликовано в RFC 4525. Пример структуры LDIF:
Записи каталога представляются группами строк, разделенных пустой строкой, при этом каждая строка в группе представляет отдельное значение атрибута записи. Первая строка в группе должна представлять уникальное имя записи DN. Значение атрибута записывается в кодировке ASCII и отделяется от его имени символом :. Значения, не подходящие под эту кодировку, записываются в кодировке base64 и отделяются от имени атрибута символами ::. Данный формат можно использовать для хранения данных, добавления и модификации записей в каталоге. Пример LDIF-файла добавления записи в каталог нового подразделения add_dp.ldif:
dn: ou=marketing,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan
objectClass: rbta-org-unit
objectClass: top
ou: marketing
displayName: Маркетинг
Примечание
В конце LDIF стоит пустая строка, которая определяет разделение между записями. Даже если запись одна, нужно ставить разделитель из пустой строки.
При модификации используется директива changetype, которая может принимать значения: add, modify, replace, delete и moddn, более подробно рассмотренные далее в примере ldapmodify.
Добавление записи в каталог через ldapadd¶
Добавление в каталог информации возможно утилитами ldapadd и ldapmodify, где ldapadd это символическая ссылка на команду ldapmodify, поэтому можно использовать ldapmodify -a. Добавьте новое подразделение через описанный выше LDIF-файл add_dp.ldif командой перенаправления <:
Ldapadd -Q < add_dp.ldif
Результат выполнения:
adding new entry "ou=marketing,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan"
В результате выводится сообщение adding new entry и DN новой записи.
Второй способ добавления – это работа с утилитой в интерактивном режиме для этого используется команда ldapadd:
ldapadd -Q
Введите текст записи в формате LDIF, в конце которого должна быть пустая строка:
dn: ou=develop,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan
objectClass: rbta-org-unit
objectClass: top
ou: develop
displayName: Разработка ПО
Мигающий курсор означает ожидание ввода символов или сигналов. Отправьте терминалу сигнал EOF командой Ctrl+.
Ctrl + <d>
Результат выполнения:
objectClass: rbta-org-unit
objectClass: top
ou: Develop
displayName: Разработка ПО
adding new entry "ou=Develop,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan"
Программа сообщит об успешном добавлении adding new entry. Для выхода без изменений отправьте другой сигнал SIGINT Ctrl +. Использовать интерактивный режим для скриптов не рекомендуется, потому что может произойти событие ожидания ввода и скрипт зависнет, ожидая ввода пользователя. Поэтому в написании скриптов для команд используют параметр -y для подтверждения всех запросов. В примерах далее использован первый подход с перенаправлением.
Модификация записей в каталоге через ldapmodify¶
Как говорилось ранее, директива changetype должна следовать сразу после dn. Это нужно для того, чтобы утилита понимала, какая запись изменяется. Если в LDIF отсутствует директива changetype, то по умолчанию подразумевается changetype: add - добавление записи в каталог.
Директивы changetype: modify и add¶
Данная директива LDIF позволяет добавить атрибут к записи, но есть атрибуты, которые могут быть только в единичном числе, например, атрибут displayName. В этом случае при добавлении второго атрибута возникнет ошибка ldap_add: Already exists (68). В примере ниже пользователю admin добавляется новая локация - атрибут L в файл add_loc.ldif:
dn: uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
changetype: modify
add: l
l: Казань
\n
Пустая строка (n) - это разделитель между записями. Ее необходимо добавлять даже при операциях с одной записью. Запуск команды модификации ldapmodify:
ldapmodify -Q < add_loc.ldif
Также ее можно передать по конвейеру:
cat add_loc.ldif | ldapmodify -Q
Результат выполнения:
modifying entry "uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
Директивы changetype: modify и replace¶
Для перемещения пользователя в другой департамент ему нужно изменить атрибут rbtadb. Сначала создается файл, например changedp.ldif:
dn: uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
changetype: modify
replace: rbtadp
rbtadp: ou=marketing,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan
А затем производится запуск команды модификации ldapmodify:
ldapmodify -Q < changedp.ldif
Результат выполнения:
modifying entry "uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
Успешное изменение записи можно проверить в Apache Directory Studio.
Директивы changetype: modify и delete¶
Если необходимо изменить несколько атрибутов у записи, то можно несколько операций разделить c новой строки символом -. Пример изменений нескольких атрибутов удаляет атрибут l: Казань и добавляет l: Москва. Сначала создается файл, например change_two_attrs.ldif:
dn: uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
changetype: modify
delete: l
l: Казань
-
add: l
l: Москва
А затем производится запуск команды модификации ldapmodify, указав файл:
change_two_attrs.ldif через параметр -f:
ldapmodify -Q -f change_two_attrs.ldif
Результат выполнения:
modifying entry "uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
Директивы changetype: modrdn и newrdn¶
Директива modrdn изменяет RDN записи, другими словами переименовывает запись. Например, переименуйте подразделение Разработки ПО ou=develop на ou=arch архитекторов через файл rename_rdn.ldif, в котором директива deleteoldrdn: 1 удалит старый DN:
dn: ou=develop,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan
changetype: modrdn
newrdn: ou=arch
deleteoldrdn: 1
dn: ou=arch,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan
changetype: modify
replace: displayName
displayName: Архитекторы
Перенапрвление файла с помощью команды ldapmodify:
rename_rdn.ldif:
ldapmodify -Q < rename_rdn.ldif
Результат выполнения:
modifying rdn of entry "ou=develop,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan"
modifying entry "ou=arch,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan"
Важно
При использовании директивы modrdn есть угроза возникновения ошибки в виде некорректного количества записей (total).
Директива changetype: delete¶
Если необходимо удалить запись из каталога, используется директива changetype: delete, однако, у записи не должно быть ни одной дочерней записи. Например, удаление группы ou=arch через файл del_ou.ldif:
dn: ou=arch,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan
changetype: delete
Проверка результата:
ldapmodify -Q < del_ou.ldif
Результат выполнения:
deleting entry "ou=arch,ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan"
Если запись будет не найдена, то выведется сообщение об ошибке:
ldap_delete: No such object (32)
matched DN: ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan
Работа с кириллицей и Unicode¶
Если атрибуты содержат значения не в ASCII-кодировке, то утилита ldapsearch при выводе значений представляет их в base64 кодировке. В примере, при создании пользователя ppetrov атрибуту displayName было присвоено значение Петров П.П.:
ldapsearch -Q -LLL -s base -b "uid=ppetrov,cn=users,cn=accounts,dc=ald,dc=company,dc=lan" displayname
Результат выполнения:
dn: uid=petrov.pp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
displayname:: 0J/QtdGC0YDQvtCyINCfLtCfLg==
Поскольку ldapsearch понимает только ASCII-символы, она представила значение displayName в неудобной для чтения base64 строке. Увидеть исходное значение можно только сделав обратное преобразование:
echo '0J/QtdGC0YDQvtCyINCfLtCfLg==' | base64 -d
Результат выполнения:
Петров П.П.
Для того, чтобы получить результат сразу в удобном для автоматизации виде, можно использовать команду:
ldapsearch -Q -LLL -s base -b "uid=ppetrov,cn=users,cn=accounts,dc=ald,dc=company,dc=lan" displayname | perl -MMIME::Base64 -Mutf8 -pe 's/^([-a-zA-Z0-9;]+):(:\s+)(\S+)$/$1.$2.&decode_ba se64($3)/e'
Результат выполнения:
dn: uid=petrov.pp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
displayName: Петров П.П.
Производить какие-то дополнительные действия в фильтрах при поиске по строкам, не содержащим ASCII-символы, не требуется:
ldapsearch -Q -LLL -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(displayName=Петров П.П.)" cn
Результат выполнения:
dn: uid=petrov.pp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
cn: Petr Petrov
Примеры автоматизации¶
В предыдущих разделах объясняется, как можно управлять каталогом через LDAP-протокол, это своего рода «кирпичики», из которых можно «строить дома», т.е. решать сложные задачи автоматизации. Ниже представлены примеры на языках bash и python, а также работа с форматами CSV, LDIF, JSON.
Работа с объектами из командной строки bash¶
В операционных системах Microsoft Windows для решения задач автоматизации раньше использовали командную оболочку и интерпретатор командной строки CMD. На замену ему пришел более мощный объектно-ориентированный PowerShell, который предоставляет удобные инструменты для работы с датами, хэш-таблицами, словарями, CSV и JSON объектами, а также имеет множество дополнительных модулей, например, для взаимодействия с каталогом Active Directory, объектной моделью офисных приложений и пр.
В мире Linux есть множество оболочек и интерпретаторов, например, sh, bash, zsh и др., каждый из которых предлагает свой вариант синтаксиса и может вызывать утилиты, доступные в операционной системе, например, ls, ping, sed, cat, cut и др. Но все они в какой-то степени являются эквивалентом CMD, т.к. работают только с текстовыми переменными и не поддерживают объекты.
Обрабатывать текстовый поток, получаемый от других приложений, средствами командной строки довольно сложно, поэтому bash целесообразно дополнить возможностями языка программирования Python, который в мире Linux можно считать эквивалентом PowerShell, по крайней мере, в части удобства работы с объектами. Далее приведены примеры скриптов Python для конвертации LDIF в JSON / CSV, и способы работы с этими данными с помощью утилит jq / cut.
Конвертер ldif2csv и генерация отчетов¶
Обрабатывать LDIF довольно сложно, т.к. данные представлены в блоках строк, а нужные атрибуты могут отсутствовать. Вот пример Python скрипта, который позволяет конвертировать поток LDIF в CSV, для его использования нужно создать файл, например ldif2csv.py, и скопировать туда следующее:
#!/usr/bin/python3
import sys
import base64
import re
if sys.version_info[0] < 3:
raise Exception("Use Python 3: python3 ldif2csv.py")
### read from sdtin
data = sys.stdin.readlines()
header_string = ""
atrcheck = ""
entry = ""
dic_entries = {}
dic_entry = {}
current_dn = ""
val_base64 = False
max_size_cell = 32000 ### limit chars in excel cell
### main loop for parse headers and collect dict
for line in data:
ln = line.replace("\n", "").replace("\r", "")
if len(ln) == 0:
if not current_dn == "":
dic_entries[current_dn] = dic_entry
dic_entry = {}
entry = ""
else:
if ln.startswith("version"):
#dic_entries["version"] = ln.split(": ")[1]
continue
elif ln.lstrip()[0] == "#":
continue
elif ln[0] == " ":
### if line wrapped line starts with " " then add line to last attr
dic_entry[atrcheck] += ln.lstrip()
if val_base64:
val_to_decode = dic_entry[atrcheck]
try:
dic_entry[atrcheck] = base64.b64decode(val_to_decode).decode(
'utf-8').strip()
except:
dic_entry[atrcheck] = val_to_decode
continue
entry += ln
attribute = []
attribute_name = ""
attribute_value = ""
if ln.find(":: ") > 0:
val_base64 = True
attribute = ln.split(":: ")
try:
attribute_value = base64.b64decode(
attribute[1]).decode('utf-8').strip()
except:
attribute_value = attribute[1]
elif ln.find(":< ") > 0:
val_base64 = False
attribute = ln.split(":< ")
attribute_value = attribute[1]
else:
val_base64 = False
attribute = ln.split(": ")
try:
attribute_value = re.sub(r"^.*?: ", "", ln)
except:
attribute_value = ""
atrcheck = attribute[0].replace(":", "")
### get attribute and check if attribute exist
if dic_entry.get(atrcheck):
new_len = len(dic_entry[atrcheck]) + len("|" + str(attribute_value))
if new_len < max_size_cell:
dic_entry[atrcheck] = str(
dic_entry[atrcheck]) + "|" + str(attribute_value)
else:
dic_entry[atrcheck] = attribute_value
if atrcheck == "dn":
current_dn = attribute[1]
if header_string.find(atrcheck) < 0:
if header_string == "":
header_string += atrcheck
else:
header_string += ";" + atrcheck
### add row if row not empty
if entry != "":
dic_entries[current_dn] = dic_entry
dic_entry = {}
entry = ""
### print utf-8-BOM and headers
print('\ufeff' + "\"" + header_string.replace(";", "\";\"") + "\"")
### print data
for d in dic_entries:
od = dic_entries[d]
csv_row = "" ### row csv value
split_char = "" ### spliter for values
for column in header_string.split(";"):
if len(csv_row) > 0:
split_char = ";" ### need split because csv row have chars
find_column = od.get(column)
if find_column and find_column.strip():
csv_row += split_char + "\"" + od[column].replace("\"", "\"\"") + "\""
else:
csv_row += split_char + "\"\""
print(csv_row)
Пример конвертации данных из ldapsearch через конвейер:
ldapsearch -Q -LLL -s one -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(uid=*)" uid displayName sn mail rbtadb | python3 ldif2csv.py
Результат выполнения:
"dn";"uid";"sn";"displayName";"mail"
"uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan";"admin";"Administrator";"";""
"uid=petrovp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan";"petrovp";"Петров";"Петров петр";"petrovp@ald.company.lan"
"uid=ivani,cn=users,cn=accounts,dc=ald,dc=company,dc=lan";"ivani";"Иванов";"Иван Иванов";"ivani@ald.company.lan"
"uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan";"petrovss";"Сидоров";"Петр С.";"petrov.ss@ald.company.lan"
Сохранить этот вывод в файл users.csv можно простым перенаправлением:
ldapsearch -Q -LLL -s one -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(uid=*)" uid displayName sn mail rbtadb | python3 ldif2csv.py > users.csv
На Файл, экспортированный с помощью ldif2csv.py, открытый в LibreOffice Calc можно увидеть, как будет выглядеть содержимое файла, если его открыть в LibreOffice Calc.
Рисунок 67 Файл, экспортированный с помощью ldif2csv.py, открытый в LibreOffice Calc¶
Далее можно использовать полученный CSV-файл следующим образом:
#!/bin/bash
csv_file=$1
cat $csv_file | while read line
do
if [ ! -z "${line}" ]; then
if [[ "${line:0:1}" == "\"" ]]; then
column1=$(echo ${line} | cut -d ";" -f 1 | sed "s/^\"//g" | sed "s/\"$//g")
column2=$(echo ${line} | cut -d ";" -f 2 | sed "s/^\"//g" | sed "s/\"$//g")
column3=$(echo ${line} | cut -d ";" -f 3 | sed "s/^\"//g" | sed "s/\"$//g")
echo -e "$column1\t$column2\t$column3"
### можете добавить свои действия по обработке данных из файла
fi
fi
done
Если сохранить приведенный скрипт в файл parse_csv.sh, то ему можно передать имя CSV-файла для обработки, как параметр. А также, необходимо назначить скрипту права на выполнение:
chmod +x ./parse_csv.sh
./parse_csv.sh users.csv
Результат выполнения:
uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan admin Administrator
uid=petrovp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan petrovp Петров
uid=ivani,cn=users,cn=accounts,dc=ald,dc=company,dc=lan ivani Иванов
uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan petrovss Сидоров
Как мы видно на примере выше, выводится несколько колонок с разделителем табуляции \t.
Конвертер ldif2json и работа с JSONPath¶
Для работы со структурированными данными можно также использовать формат JSON, и в Linux есть очень удобный процессор JSON, который называется jq. Вот пример Python-скрипта, который позволяет конвертировать поток LDIF в JSON, для его использования нужно создать файл, например ldif2json.py, и скопировать туда следующее содержимое:
#!/usr/bin/python3
import sys
import base64
import json
import re
### parse bool and int to json
def parce_value(value):
try:
return int(value)
except:
if value == "FALSE" or value == "FALSE": return bool(value)
else: return value
if sys.version_info[0] < 3:
raise Exception("Use Python 3: python3 ldif2json.py")
data = sys.stdin.readlines()
header_string = ""
atrcheck = ""
entry = ""
dic_entries = {}
dic_entry = {}
current_dn = ""
val_base64 = False
### main loop for parse headers and collect dict
for line in data:
ln = line.replace("\n", "").replace("\r", "")
if len(ln) == 0:
if not current_dn == "":
dic_entries[current_dn] = dic_entry
dic_entry = {}
entry = ""
else:
if ln.startswith("version"):
dic_entries["version"] = ln.split(": ")[1]
continue
elif ln.lstrip()[0] == "#":
continue
### if line wrapped line starts with " " then add line to last attr
elif ln[0] == " ":
dic_entry[atrcheck] += ln.lstrip()
if val_base64:
val_to_decode = dic_entry[atrcheck]
try:
dic_entry[atrcheck] = base64.b64decode(val_to_decode).decode(
'utf-8').strip()
except:
dic_entry[atrcheck] = val_to_decode
continue
entry += ln
attribute = []
attribute_name = ""
attribute_value = ""
if ln.find(":: ") > 0:
val_base64 = True
attribute = ln.split(":: ")
try:
attribute_value = base64.b64decode(
attribute[1]).decode('utf-8').strip()
except:
attribute_value = attribute[1]
elif ln.find(":< ") > 0:
val_base64 = False
attribute = ln.split(":< ")
attribute_value = attribute[1]
else:
val_base64 = False
attribute = ln.split(": ")
try:
attribute_value = re.sub(r"^.*?: ", "", ln)
except:
attribute_value = ""
atrcheck = attribute[0].replace(":", "")
### get attribute and check if attribute exist
if dic_entry.get(atrcheck):
dic_entry[atrcheck] = str(
dic_entry[atrcheck]) + "|" + str(attribute_value)
else:
dic_entry[atrcheck] = parce_value(attribute_value)
if atrcheck == "dn":
current_dn = attribute[1]
if header_string.find(atrcheck) < 0:
if header_string == "":
header_string += atrcheck
else:
header_string += ";" + atrcheck
### add row if row not empty
if entry != "":
dic_entries[current_dn] = dic_entry
dic_entry = {}
entry = ""
### print stdout json from dict
print(json.dumps(dic_entries, ensure_ascii=False))
Пример конвертации данных из ldapsearch через конвейер:
ldapsearch -Q -LLL -s one -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(uid=*)" uid displayName sn mail rbtadb | python3 ldif2json.py
Результат выполнения:
{
"uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan": {
"dn": "uid=admin,cn=users,cn=accounts,dc=ald,dc=company,dc=lan",
"uid": "admin",
"sn": "Administrator"
},
"uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan": {
"dn": "uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan",
"uid": "petrovss",
"displayName": "Петр С.",
"sn": "Сидоров",
"mail": "petrov.ss@ald.company.lan"
},
}
Сохранить этот вывод в файл users.json можно простым перенаправлением и далее с помощью утилиты jq из одноименного пакета извлекать любые данные с помощью запросов JSONPath. Например, чтобы узнать значение атрибута mail для пользователя petrovss нужно ввести следующее:
ldapsearch -Q -LLL -s one -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(uid=*)" uid displayName sn mail rbtadb | python3 ldif2json.py > users.json && jq -r '."uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan".mail' users.json
где:
параметр
-r– означает, что выводить нужно сырые данные без кавычек;".”uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan.mail"- это текст запроса JSONPath.
Результат выполнения:
petrov.ss@ald.company.lan
Для обработки полученных JSON данных в цикле, следует создать файл, например json_cycle.sh, со следующим содержимым:
#!/bin/bash
echo -e "uid\tdisplayName\tsn\tmail"
ldapsearch -Q -LLL -s one -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(uid=*)" uid displayName sn mail rbtadb | python3 ldif2json.py > users.json
cat users.json | jq -r 'delpaths([paths | select(length > 1)])' | jq -r '[paths | join(".")]'| jq -r 'join("\n")' | while read key
do
uid=$(jq -r ".\"$key\".uid" users.json)
mail=$(jq -r ".\"$key\".mail" users.json)
displayName=$(jq -r ".\"$key\".displayName" users.json)
sn=$(jq -r ".\"$key\".sn" users.json)
echo -e "$uid\t$displayName\t$sn\t$mail"
### можете добавить свои действия по обработке данных из файла
done
Результат выполнения:
uid displayName sn mail
admin null Administrator null
petrovp Петров петр Петров petrovp@ald.company.lan
ivani Иван Иванов Иванов ivani@ald.company.lan
petrovss Петр С. Сидоров petrov.ss@ald.company.lan
Добавление пользователей¶
В этом примере при добавлении пользователя objectClass класс с именем ipantuserattrs не добавляется, чтобы атрибут ipaNTSecurityIdentifier сгенерировался автоматически. Содержимое файла add_user.ldif:
dn: uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
objectClass: top
objectClass: person
objectClass: organizationalperson
objectClass: inetorgperson
objectClass: inetuser
objectClass: posixaccount
objectClass: krbprincipalaux
objectClass: krbticketpolicyaux
objectClass: ipaobject
objectClass: ipasshuser
objectClass: x-ald-user
objectClass: x-ald-user-parsec14
objectClass: x-ald-audit-policy
objectClass: ruPostMailAccount
objectClass: rbtaCustomUserAttrs
objectClass: rbtaUserMeta
objectClass: rbta-unit
objectClass: rbta-address
objectClass: rbta-inetorgperson-ext
objectClass: ipaSshGroupOfPubKeys
objectClass: mepOriginEntry
givenName: Петр
sn: Сидоров
uid: petrovss
cn: petrov.ss
uidNumber: -1
gidNumber: -1
displayName: Петр С.
initials: P.С.
gecos: Тел. +71234567890
rbtamiddlename: Сидоров
rbtadp: ou=ald.company.lan,cn=orgunits,cn=accounts,dc=ald,dc=company,dc=lan
loginShell: /bin/bash
homeDirectory: /home/petrov.ss
mail: petrov.ss@ald.company.lan
x-ald-user-mac: 0:0x0:0:0x0
krbCanonicalName: petrov.ss@ALD.COMPANY.LAN
krbPrincipalName: petrov.ss@ALD.COMPANY.LAN
userPassword: somepassword
Примечание
Атрибуты uidNumber и gidNumber нужно устанавливать равными -1, в этом случае DNA-плагин автоматически сгенерирует значения идентификаторов при добавлении пользователя. Пароль следует передавать открытым текстом. Он будет хэширован алгоритмом PBKDF2-SHA256 автоматически. Записать пользователю, сгенерированный где-то в другом месте hash-пароля возможно только при создании пользователя, если сервер переведен в режим миграции.
Далее выполняется команда по добавлению нового пользователя:
ldapadd -Q < add_user.ldif
Результат выполнения:
adding new entry "uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
После входа пользователю нужно будет задать новый пароль, так как пароль назначенный таким способом является временным.
Теперь нужно добавить пользователей в цикле, используя данные из CSV-файла - создать файл, например cycleadd.csv, c разделителем ; в любом тектовом редакторе:
givenName;sn;uid
Александр;Петров;alexp
Михаил;Иванов;mikhaili
Артём;Сидоров;artems
Затем создается скрипт add_from_csv.sh по добавлению пользователей из CSV-файла:
#!/bin/bash
csv_file=$1
delim=$2
function template()
{
psw=$(cat /dev/urandom| tr -dc '0-9a-zA-Z!@#$%^&*_+-' | head -c 14;echo;)
uid=$1 #установить uid из первого параметра
givenName=$2 #установить Имя из второго
sn=$3 #установить Фамилию из третьего
suffix=$(ldapsearch -Q -LLL -s base|grep 'dn:'|cut -d ' ' -f2) #получить suffix домена
realm=$(hostname -d|tr '[a-zA-Z]' '[A-Za-z]')
domain=$(hostname -d)
cat <<EOF
dn: uid=$uid,cn=users,cn=accounts,$suffix
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalperson
objectClass: inetorgperson
objectClass: inetuser
objectClass: posixaccount
objectClass: krbprincipalaux
objectClass: krbticketpolicyaux
objectClass: ipaobject
objectClass: ipasshuser
objectClass: x-ald-user
objectClass: x-ald-user-parsec14
objectClass: x-ald-audit-policy
objectClass: ruPostMailAccount
objectClass: rbtaCustomUserAttrs
objectClass: rbtaUserMeta
objectClass: rbta-unit
objectClass: rbta-address
objectClass: rbta-inetorgperson-ext
objectClass: ipaSshGroupOfPubKeys
objectClass: mepOriginEntry
givenName: $givenName
sn: $sn
uid: $uid
cn: $uid
uidNumber: -1
gidNumber: -1
displayName: $givenName ${sn:0:1}.
initials: ${givenName:0:1}. ${sn:0:1}.
rbtadp: ou=ald.company.lan,cn=orgunits,cn=accounts,$suffix
loginShell: /bin/bash
homeDirectory: /home/$uid
mail: $uid@$domain
x-ald-user-mac: 0:0x0:0:0x0
krbCanonicalName: $uid@$realm
krbPrincipalName: $uid@$realm
userPassword: $psw
EOF
}
echo Добавление пользователей из $csv_file
truncate -s 0 cycleadd.ldif
chmod 500 ./cycleadd.ldif
while read line; do let c++;
if [ $c -gt 1 ]; then
p_givenName=$(echo ${line} | cut -d $delim -f 1 | sed "s/^\"//g" | sed "s/\"$//g" | sed "s/^\'//g" | sed "s/\'$//g")
p_sn=$(echo ${line} | cut -d $delim -f 2 | sed "s/^\"//g" | sed "s/\"$//g"| sed "s/^\'//g" | sed "s/\'$//g")
p_uid=$(echo ${line} | cut -d $delim -f 3 | sed "s/^\"//g" | sed "s/\"$//g" | sed "s/^\'//g" | sed "s/\'$//g")
template $p_uid $p_givenName $p_sn >> cycleadd.ldif
fi
done < $csv_file
### добавим пользователей одним пакетом
ldapadd -c -f cycleadd.ldif
Функцией template описан шаблон одной записи. Этот шаблон можно настроить по своему усмотрению, прописав свои корневые суффиксы и реалмы.
Далее следует назначить права запуска скрипту add_from_csv.sh и запустить его:
chmod +x ./add_from_csv.sh && ./add_from_csv.sh cycleadd.csv ';'
Результат выполнения:
Добавление пользователей из cycleadd.csv
adding new entry "uid=alexp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
adding new entry "uid=mikhaili,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
adding new entry "uid=artems,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
Скрипт add_from_csv.sh создал временный LDIF-файл cycleadd.ldif и передал команде ldapadd. После чего можно создать новый файл new_users.csv уже после добавления пользователей, потому что там уже сохранены новые пароли пользователей:
cat cycleadd.ldif | python3 ldif2csv.py > new_users.csv
Файл new_users.csv можно скачать, для последующей рассылки пользователям паролей и учетных данных для входа, см Файл с новыми паролями для добавленных в цикле пользователей.
Рисунок 68 Файл с новыми паролями для добавленных в цикле пользователей¶
Смена пароля пользователей¶
В случае взлома системы необходимо изменить пароли всех пользователей. Для этого создается скрипт на языке bash change_all_pass.sh:
#!/bin/sh
read -p "Вы хотите создать новые пароли всем пользователям (Yes|no)? " yn
if [[ $yn =~ "Yes" ]]; then
echo '"dn";"password"' > new_passwords.csv
echo "" > tmp_new_pass
ldapsearch -Q -LLL -s one -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(!(uid=admin))" dn | while read line
do
if [ ! -z "${line}" ]; then
psw=$(cat /dev/urandom| tr -dc '0-9a-zA-Z!@#$%^&*_+-' | head -c 14;echo;)
echo "\"$line\";\"$psw\"" >> new_passwords.csv
echo -e "${line}\nchangetype: modify\nreplace: userPassword\nuserPassword: $psw\n" >> tmp_new_pass
fi
done
ldapmodify -Q < tmp_new_pass
fi
Запуск скрипта:
chmod +x change_all_pass.sh && ./change_all_pass.sh
Результат выполнения:
Вы хотите создать новые пароли всем пользователям (Yes|no)? Yes
modifying entry "uid=petrovp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
modifying entry "uid=ivani,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
modifying entry "uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
Далее следует подготовить CSV-файл newpass.csv с паролями из временного tmp_new_pass LDIF через конвертер ldif2CSV.py:
cat tmp_new_pass | python3 ldif2csv.py > newpass.csv
Результат выполнения:
"dn";"changetype";"replace";"userPassword"
"uid=petrovp,cn=users,cn=accounts,dc=ald,dc=company,dc=lan";"modify";"userPassword";"l@q%yTU1hf_EP7"
"uid=ivani,cn=users,cn=accounts,dc=ald,dc=company,dc=lan";"modify";"userPassword";"nM^ZER_z_sULpE"
"uid=petrovss,cn=users,cn=accounts,dc=ald,dc=company,dc=lan";"modify";"userPassword";"oxK*HzeFmGJw5w"
А также можно открыть файл newpass.csv в редакторе электронных таблиц, см. Пакетное изменение паролей пользователей.
Рисунок 69 Пакетное изменение паролей пользователей¶
Проверка просроченных паролей¶
Проверка просроченных паролей пользователей с помощью скрипта check_expired.sh:
#!/bin/bash
### 1. Получить список пользователей из DN cn=users,cn=accounts,dc=ald,dc=company,dc=lan
### 2. Отфильтровать список с полями uid, displayname, mail, krbPasswordExpiration
### 3. Оставить пользователей, у которых дата просрочки пароля (krbPasswordExpiration) от даты сегодня до даты сегодня + 7 дней
ldapsearch -Q -LLL -o ldif-wrap=no -b "cn=users,cn=accounts,dc=ald,dc=company,dc=lan" "(&(krbPasswordExpiration>=$(date +"%Y%m%d000000Z"))(krbPasswordExpiration<=$(date +"%Y%m%d000000Z" -d "+7 days")))" uid displayname mail krbPasswordExpiration > expired_ldif
while read line
do ### 4. Цикл пользователь из пользователей
if [ -z "${line}" ]; then
if [ ! -z $user_mail ]; then
### 4.1 Если почта у пользователя есть
timeExp=$(date -d "${date_expire:0:4}-${date_expire:4:2}-${date_expire:6:2} ${date_expire:8:2}:${date_expire:10:2}:${date_expire:12:2}Z" +"%s")
timeNow=$(date +"%s")
seconds=$(( timeExp - timeNow ))
min=$(( seconds / 60 ))
hours=$(( min / 60 ))
days=$(( hours / 24 ))
ago=$(echo "через $daysдн.")
if [ $timeNow -gt $timeExp ]; then
ago="просрочен!!"
fi
dateprint=$(date -d "@$timeExp")
echo "$user_name ($user_mail), пароль просрочится ${dateprint} $ago"
### Тут вы можете добавить свою обработку пользователя.
else
### 4.3 Иначе добавить пользователя в лог
echo "$(date) [expire.sh] uid $user_uid mail is epmty" >> "expired.log"
fi
### очистим переменные для следующей записи
user_uid=""
user_name=""
user_mail=""
date_expire=""
else
attr=$(echo $line | cut -d ":" -f 1)
attvalue=$(echo $line | cut -d ":" -f 2)
if [ -z "$attvalue" ]; then
attvalue=$(echo $line | cut -d " " -f 2 | base64 -d)
fi
### проверка атрибута и присвоение переменной значения
case $attr in
uid) user_uid=$(echo $attvalue | xargs echo -n);;
displayname) user_name=$(echo $attvalue | xargs echo -n) ;;
mail) user_mail=$(echo $attvalue | xargs echo -n);;
krbPasswordExpiration) date_expire=$(echo $attvalue | xargs echo -n);;
esac
fi
done < expired_ldif
Следует указать атрибут выполнения и запустить скрипт:
chmod +x ./check_expired.sh && ./check_expired.sh
Результат выполнения:
Петров петр (petrovp@ald.company.lan), пароль просрочится Вт мая 30 12:18:21 MSK 2023 через 2дн.
Александр П. (alexp@ald.company.lan), пароль просрочится Сб мая 27 12:46:42 MSK 2023 просрочен!!
Михаил И. (mikhaili@ald.company.lan), пароль просрочится Сб мая 27 12:46:42 MSK 2023 просрочен!!
Артём С. (artems@ald.company.lan), пароль просрочится Сб мая 27 12:46:42 MSK 2023 просрочен!!
Скрипт обработал даты просрочки пароля, указав просроченные пароли и дни до их завершения. В данном примере использованы только bash и вывод ldapsearch. Также можно добавить в скрипт обработку просроченных учетных записей. Например, отправку сообщения в корпоративный мессенджер или на электронную почту.
Расширение схемы пользовательскими классами и атрибутами¶
При работе в домене ALD Pro появляется потребность в создании пользовательских атрибутов и классов для расширения схемы данных каталога, когда стандартные классы и атрибуты не позволяют описать в полной мере существующие объекты. Далее показано несколько способов реализации этой задачи. Для примеров использованы два контроллера домена: dc-1.ald.company.lan и dc-2.ald.company.lan.
Выбор OID¶
В каталоге классы и атрибуты имеют свой уникальный номер OID. Чтобы получить OID, необходимо обратиться к региональным или международным организациям, например, IANA, которые занимаются их регистрацией. На сайте https://oidref.com/org/marina-ignatyeva указаны контакты организации, которая ответственна за регистрацию OID, начинающихся с 1.2.643. Базовый OID 1.2.643 специально выделен для России, а 1.2.643.6 для различных организаций внутри страны (https://oidref.com/1.2.643). Если ваша организация уже имеет свой собственный OID, то лучше выбрать номера в соответствии с вашими внутренними правилами. Зарегистрированные номера предприятий можно посмотреть: https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers. При использовании OID, которые еще не зарегистрированы, не рекомендуется публикация новой схемы данных вне своей организации. При назначении OID атрибутам и классам, следует быть внимательным, использование одного и того же номера может привести к проблемам в работе LDAP-каталога. Префиксы OID 1.3.6.1.4.1.32702 и 1.3.6.1.4.1.52616 являются зарезервированными и используется для служебных классов и атрибутов ALD Pro и Astra Linux. Префикс OID 2.16.840.1.113730.3.8 используется для служебных классов и атрибутов FreeIPA. Их использование для расширения схемы может привести к сбоям в работе системы и невозможности ее корректного обновления.
В примерах выбран свободный незарегистрированный OID, то есть в данный момент не принадлежащий какой-либо организации. OID состоит чисел, разделенных точками, число цифр не должно превышать 128. Числа в OID могут иметь значения до 2^64-1. Cледует сгенерировать три больших числа, для того чтобы уменьшить вероятность их совпадения при выборе OID:
dc-1:~$ shuf -n 3 -i 1000000000-3000000000
2685147094
2741111200
2726881669
Первое число соответствует номеру организации, поэтому базовый OID - 1.2.643.6.2685147094. В примере, внутри организации есть несколько подразделений и сервисов, за которые отвечают эти подразделения. Одному из подразделений присвоен номер 2741111200 и номер 2726881669 сервису или приложению, за которое ответственно это подразделение. В итоге OID для подразделения внутри организации будет 1.2.643.6.2685147094.2741111200.2726881669. Атрибутам сервиса соответствует число 1, а классам число 2. Поэтому OID для атрибутов начинается с 1.2.643.6.2732288946.2741111200.2726881669.1, а для классов с 1.2.643.6.2732288946.2741111200.2726881669.2.
В итоге OID - 1.2.643.6.2685147094.2741111200.2726881669.x.y:
1.2.643.6– номер, выделенный для различных организаций в России;2732288946– номер организации;2741111200– номер подразделения внутри организации;2726881669– номер сервиса или приложения;x– тип объекта: атрибут или класс;y– номер атрибута или класса, каждый атрибут и класс должен иметь свой уникальный номер.
Возможны другие варианты выбора OID, его составных частей, это зависит от принятой иерархии и правил внутри организации.
Создание классов и объектов¶
В примере требуется создать новый класс и новые атрибуты для хранения информации о рабочем месте пользователя. Рабочее место включает в себя номер кабинета, номер рабочего стола. Новый класс будет называться workplace и содержать два атрибута: название кабинета idCabinet и номер стола idTable. Есть возможность не создавать новый класс, а атрибуты добавить в существующий класс, например, person, но так делать не рекомендутся, желательно не менять базовую схему каталога.
Примеры команд были запущены на стенде, состоящем из двух контроллеров домена dc-1.ald.company.lan и dc-2.ald.company.lan. Команды в примерах могут быть запущены на любом компьютере домена, кроме утилиты dsconf, она доступна только на контроллерах домена и должна быть запущена под пользователем root.
Изменение схемы с помощью LDIF-файла¶
Подготовка LDIF-файла для использования с утилитой ldapmodify и добавление в него определения класса и атрибутов:
dc-1:~$ cat workplace.ldif
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 1.2.643.6.2732288946.2741111200.2726881669.1.100 NAME 'idCabinet' DESC 'Cabinet number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' )
-
add: attributeTypes
attributeTypes: ( 1.2.643.6.2732288946.2741111200.2726881669.1.101 NAME 'idTable' DESC 'Table number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'user defined' )
-
add: objectClasses
objectClasses: ( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MAY ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
В файле:
OIDатрибутаidCabinetравен1.2.643.6.2732288946.2741111200.2726881669.1.100, в нем будет содержаться строковый тип данных, которому соответствуетOID1.3.6.1.4.1.1466.115.121.1.15. Например, “Приемная” или “Отдел статистики”.OIDатрибутаidTableравен1.2.643.6.2732288946.2741111200.2726881669.1.101, он будет содержать числовой тип данных, которому соответствуетOID1.3.6.1.4.1.1466.115.121.1.27.OIDклассаworkplaceравен1.2.643.6.2732288946.2741111200.2726881669.2.50, он имеет родительский классtop. Класс содержит необязательные атрибутыidCabinet,idTable, если они должны быть обязательными, необходимо поменятьMAYнаMUST.
Теперь следует добавить новые данные схемы в каталог:
dc-1:~$ ldapmodify -D "cn=Directory Manager" -W -f workplace.ldif
Enter LDAP Password:
modifying entry "cn=schema"
В cn=schema должны появиться новые данные:
dc-1:~# dsconf -D "cn=Directory Manager" - W ldap://localhost schema objectclasses query workplace
Enter password for cn=Directory Manager on ldap://localhost:
( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MUST ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
dc-1:~#
dc-1:~# dsconf -D "cn=Directory Manager" - W ldap://localhost schema attributetypes query idCabinet
Enter password for cn=Directory Manager on ldap://localhost:
( 1.2.643.6.2732288946.2741111200.2726881669.1.100 NAME 'idCabinet' DESC 'Cabinet number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' )
MUST
MAY
( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MUST ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
dc-1:~#
dc-1:~# dsconf -D "cn=Directory Manager" - W ldap://localhost schema attributetypes query idTable
Enter password for cn=Directory Manager on ldap://localhost:
( 1.2.643.6.2732288946.2741111200.2726881669.1.101 NAME 'idTable' DESC 'Table number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'user defined' )
MUST
MAY
( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MUST ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
Текущая схема данных пока не обновлена, если была допущена какая-то неточность, надо удалить созданные классы и атрибуты, а затем снова создать их. Удаление будет рассмотрено в следующем разделе. Если все нормально и нет ошибок, для обновления схемы необходимо выполнить следующую команду и проверить результат:
dc-1:~# dsconf -D "cn=Directory Manager" -W ldap://localhost schema reload
Enter password for cn=Directory Manager on ldap://localhost:
Attempting to add task entry... This will fail if Schema Reload plug-in is not enabled.
Successfully added task entry cn=schema_reload_2023-11-21T16:53:52.560422,cn=schema reload task,cn=tasks,cn=config
To verify that the schema reload operation was successful, please check the error logs.
Если добавление прошло успешно, в лог файле /var/log/dirsrv/slapd-ALD-COMPANY-LAN/errors должны быть следующие строки:
dc-1:~$ vi /var/log/dirsrv/slapd-ALD-COMPANY-LAN/errors
...
[23/Nov/2023:07:21:37.108569846 +0300] - INFO - schemareload - schemareload_thread - Schema reload task starts (schema dir: default) ...
[23/Nov/2023:07:21:37.249203959 +0300] - INFO - schemareload - schemareload_thread - Schema validation passed.
[23/Nov/2023:07:21:37.370369733 +0300] - INFO - schemareload - schemareload_thread - Schema reload task finished.
...
Обновленная схема данных реплицируется на другие контроллеры домена не сразу, а только в том случае, если в каталоге произойдут какие-либо изменения, например, изменения какого-либо объекта каталога.
Создание нового класса к записи пользователя в каталоге для проверки, что схема данных реплицировалась на другой контроллер домена:
dc-1:~$ ldapmodify -D "cn=Directory Manager" -W
Enter LDAP Password:
dn: uid=user0,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
changetype: modify
add: objectClass
objectClass: workplace
modifying entry "uid=user0,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
Если теперь проверить на втором контроллере домена, то новый класс и новые атрибуты отобразятся в его каталоге:
dc-2:~# dsconf -D "cn=Directory Manager" - W ldap://localhost schema objectclasses query workplace
Enter password for cn=Directory Manager on ldap://localhost:
( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MUST ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
dc-2:~#
dc-2:~# dsconf -D "cn=Directory Manager" - W ldap://localhost schema attributetypes query idCabinet
Enter password for cn=Directory Manager on ldap://localhost:
( 1.2.643.6.2732288946.2741111200.2726881669.1.100 NAME 'idCabinet' DESC 'Cabinet number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' )
MUST
MAY
( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MUST ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
dc-2:~#
dc-2:~# dsconf -D "cn=Directory Manager" - W ldap://localhost schema attributetypes query idTable
Enter password for cn=Directory Manager on ldap://localhost:
( 1.2.643.6.2732288946.2741111200.2726881669.1.101 NAME 'idTable' DESC 'Table number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'user defined' )
MUST
MAY
( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MUST ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
Удаление пользовательских классов и атрибутов¶
Если после добавления классов и атрибутов изменения не были применены, то есть не была выполнена команда reload, для удаления достаточно запустить команды dcsonf или ldapmodify. Если же была применена команда reload и произошла репликация схемы на другие контроллеры, то перед удалением на каждом контроллере домена необходимо отключить репликацию схемы. Иначе новая схема снова появится на контроллере, на котором она была удалена, из-за репликации с другими контроллерами, так как они будут включать в себя более полный набор атрибутов и классов, включая новые данные. Когда один контроллер содержит схему с большим набором, чем второй, он реплицирует свой набор на первый. Сначала надо отключить репликацию схемы на каждом контроллере. В данном примере на двух.
На первом контроллере:
dc-1:~# dsconf -D "cn=Directory Manager" -W ALD-COMPANY-LAN config replace nsslapd-schemareplace=off
Enter password for cn=Directory Manager on ALD-COMPANY-LAN:
Successfully replaced "nsslapd-schemareplace"
На втором контроллере:
dc-2:~# dsconf -D "cn=Directory Manager" -W ALD-COMPANY-LAN config replace nsslapd-schemareplace=off
Enter password for cn=Directory Manager on ALD-COMPANY-LAN:
Successfully replaced "nsslapd-schemareplace"
После этого можно удалить новую схему с помощью утилиты dsconf или ldapmodify, не опасаясь снова ее появления из-за репликации. Для удаления созданных атрибутов и классов команды запускаются на каждом контроллере:
dc-n:~# dsconf -D "cn=Directory Manager" -W ldap://localhost schema objectclasses remove workplace
Enter password for cn=Directory Manager on ldap://localhost:
Successfully removed the objectClass
dc-n:~#
dc-n:~# dsconf -D "cn=Directory Manager" -W ldap://localhost schema attributetypes remove idCabinet
Enter password for cn=Directory Manager on ldap://localhost:
Successfully removed the attributetype
dc-n:~$
dc-n:~$ dsconf -D "cn=Directory Manager" -W ldap://localhost schema attributetypes remove idTable
Enter password for cn=Directory Manager on ldap://localhost:
Successfully removed the attributetype
Или удаление с помощью ldapmodify:
dc-n:~# cat workplace_delete.ldif
dn: cn=schema
changetype: modify
delete: objectClasses
objectClasses: ( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MAY ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
-
delete: attributeTypes
attributeTypes: ( 1.2.643.6.2732288946.2741111200.2726881669.1.100 NAME 'idCabinet' DESC 'Cabinet number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' )
-
delete: attributeTypes
attributeTypes: ( 1.2.643.6.2732288946.2741111200.2726881669.1.101 NAME 'idTable' DESC 'Table number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'user defined' )
dc-n:~#
dc-n:~# ldapmodify -v -D "cn=Directory Manager" -W -f workplace_delete.ldif
ldap_initialize( <DEFAULT> )
delete objectClasses:
( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MAY ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
delete attributeTypes:
( 1.2.643.6.2732288946.2741111200.2726881669.1.100 NAME 'idCabinet' DESC 'Cabinet number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' )
delete attributeTypes:
( 1.2.643.6.2732288946.2741111200.2726881669.1.101 NAME 'idTable' DESC 'Table number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'user defined' )
modifying entry "cn=schema"
modify complete
После удаления обязательно выполнить reload на каждом контроллере:
dc-n:~# dsconf -D "cn=Directory Manager" -W ldap://localhost schema reload
Enter password for cn=Directory Manager on ldap://localhost:
Attempting to add task entry... This will fail if Schema Reload plug-in is not enabled.
Successfully added task entry cn=schema_reload_2023-11-21T16:53:52.560422,cn=schema reload task,cn=tasks,cn=config
To verify that the schema reload operation was successful, please check the error logs.
dc-n:~#
dc-n:~# dsconf -D "cn=Directory Manager" -W ldap://localhost schema objectclasses query workplace
Enter password for cn=Directory Manager on ldap://localhost:
None
Необходимо убедиться, что в лог-файле error нет ошибок:
dc-1:~# tail /var/log/dirsrv/slapd-ALD-COMPANY-LAN/errors
...
[27/Nov/2023:07:04:59.065768985 +0300] - INFO - schemareload - schemareload_thread - Schema reload task starts (schema dir: default) ...
[27/Nov/2023:07:04:59.192963349 +0300] - INFO - schemareload - schemareload_thread - Schema validation passed.
[27/Nov/2023:07:04:59.314138040 +0300] - INFO - schemareload - schemareload_thread - Schema reload task finished.
После этого включить репликацию обратно на каждом контроллере:
dc-n:~# dsconf -D "cn=Directory Manager" -W ALD-COMPANY-LAN config replace nsslapd-schemareplace=replication-only
Enter password for cn=Directory Manager on ALD-COMPANY-LAN:
Successfully replaced "nsslapd-schemareplace"
Изменение схемы с помощью файла схемы¶
Создание пользовательского класса и атрибутов в отдельном файле схемы и интеграция в директорию /etc/dirsrv/slapd-instance_name/schema/. В данном примере это /etc/dirsrv/slapd-ALD-COMPANY-LAN/schema/. Имя файла и его содержимое должно удовлетворять нескольким критериям:
в самом начале файла должна быть строка dn:
cn=schema;название файла должно быть в формате
[1-9][0-9]text.ldifи всегда начинаться с двух цифр;важно, чтобы число в имени файла было меньше или равно числу в имени
99user.ldif;в случае, если число в имени равно
99, то в отсортированном алфавитном порядкеtextдолжен быть ниже, чем имяuserв имени файла99user.ldif. Например, имя99z.ldifбудет неверным, а99a.ldifверным, так как буква “a” расположена в алфавите раньше буквы “u”;в файле атрибуты и классы могут быть определены одновременно или по отдельности, причем сначала должны идти описания атрибутов, а затем классов;
в файле могут быть использованы уже существующие атрибуты, описанные в других классах.
LDAP-сервер будет читать файлы в директории /etc/dirsrv/slapd-ALD-COMPANY-LAN/schema/ по порядку, начиная с файлов с именами, содержащими меньшие числа и заканчивая файлом с именем 99user.ldif. Если числа одинаковы, то в алфавитном порядке. Сервер ожидает, что самым последним прочитанным файлом будет 99user.ldif. Когда добавляются новые элементы схемы, сервер записывает добавленные пользователем атрибуты и классы в файл, имя которого в директории /etc/dirsrv/slapd-ALD-COMPANY-LAN/schema/ стоит последним после сортировки. Если имя последнего файла не совпадает с 99user.ldif, в работе каталога могут быть проблемы. Файл 99user.ldif* является служебным и всегда должен читаться сервером последним, напрямую в него не рекомендуется добавлять новую схему. Если в файлах встречаются определения одинаковых атрибутов и классов, определения загруженные позже перезаписывают предыдущие.
Нужно создать файл 99a.ldif и добавить в него определения класса workplace и атрибутов idCabinet, idTable.
dc-1:~# cat /etc/dirsrv/slapd-ALD-COMPANY-LAN/schema/99a.ldif
dn: cn=schema
attributeTypes: ( 1.2.643.6.2732288946.2741111200.2726881669.1.100 NAME 'idCabinet' DESC 'Cabinet number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'user defined' )
attributeTypes: ( 1.2.643.6.2732288946.2741111200.2726881669.1.101 NAME 'idTable' DESC 'Table number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'user defined' )
objectClasses: ( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workplace' DESC 'Workplace for employee' SUP top AUXILIARY MAY ( idCabinet $ idTable ) X-ORIGIN 'user defined' )
Применение новой схемы:
dc-1:~# dsconf -D "cn=Directory Manager" -W ldap://localhost schema reload
Enter password for cn=Directory Manager on ldap://localhost:
Attempting to add task entry... This will fail if Schema Reload plug-in is not enabled.
Successfully added task entry cn=schema_reload_2023-11-27T07:43:36.534129,cn=schema reload task,cn=tasks,cn=config
To verify that the schema reload operation was successful, please check the error logs.
После репликации файл 99a.ldif не будет скопирован на второй контроллер, а новый класс и новые атрибуты появятся на нем в файле /etc/dirsrv/slapd-ALD-COMPANY-LAN/schema/99user.ldif.
dc-2:~# grep -A 2 "workpl\|idCab\|idTab" /etc/dirsrv/slapd-ALD-COMPANY-LAN/schema/99user.ldif
objectClasses: ( 1.2.643.6.2732288946.2741111200.2726881669.2.50 NAME 'workpla
ce' DESC 'Workplace for employee' SUP top AUXILIARY MAY ( idCabinet $ idTable
) X-ORIGIN 'user defined' )
attributeTypes: ( 2.16.840.1.113730.3.1.2384 NAME 'passwordTPRDelayValidFrom'
--
attributeTypes: ( 1.2.643.6.2732288946.2741111200.2726881669.1.100 NAME 'idCab
inet' DESC 'Cabinet number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE X-ORIGIN 'user defined' )
--
attributeTypes: ( 1.2.643.6.2732288946.2741111200.2726881669.1.101 NAME 'idTab
le' DESC 'Table number for employee' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SIN
GLE-VALUE X-ORIGIN 'user defined' )
Классы по умолчанию для пользователей и групп пользователей¶
В данный момент, при создании пользователей на Портале Управления ALD Pro записям пользователей в каталоге, находящимся в cn=users,cn=accounts,dc=ald,dc=company,dc=lan, новый созданный класс не будет добавлен автоматически. Если создать пользователя user1 и посмотреть список классов, он будет выглядеть так:
dc-1:~# ldapsearch -Q -LLL -D "cn=Directory Manager" -W -b "uid=user1,cn=users,cn=accounts,dc=ald,dc=company,dc=lan" objectclass
Enter LDAP Password:
dn: uid=user0,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
objectclass: top
objectclass: person
objectclass: x-ald-user
objectclass: x-ald-user-parsec14
objectclass: x-ald-audit-policy
objectclass: rbta-unit
objectclass: rbta-address
objectclass: rbtaCustomUserAttrs
objectclass: rbta-inetorgperson-ext
objectclass: ruPostMailAccount
objectclass: rbtaUserMeta
objectclass: ipaSshGroupOfPubKeys
objectclass: mepOriginEntry
objectclass: organizationalPerson
objectclass: inetOrgPerson
objectclass: inetUser
objectclass: posixAccount
objectclass: krbPrincipalAux
objectclass: krbTicketPolicyAux
objectclass: ipaObject
objectclass: ipaSshUser
objectclass: ipaNTUserAttrs
Команда ipa config-show покажет, какие классы по умолчанию добавляются к записи пользователя и к записи группы при их создании. Команда ipa должна быть запущена под администратором домена. Для этого надо войти под ним в систему или пройти kerberos-аутентификацию с помощью kinit. Атрибут ipaUserObjectClasses в записи cn=ipaConfig,cn=etc,dc=ald,dc=company,dc=lan содержит все классы пользователя, а ipaGroupObjectClasses все классы для группы.
dc-1:~# ipa config-show --all --raw
dn: cn=ipaConfig,cn=etc,dc=ald,dc=company,dc=lan
...
cn: ipaConfig
ipaGroupObjectClasses: top
ipaGroupObjectClasses: groupofnames
ipaGroupObjectClasses: nestedgroup
ipaGroupObjectClasses: ipausergroup
ipaGroupObjectClasses: ipaobject
ipaGroupObjectClasses: x-ald-audit-policy
ipaGroupObjectClasses: rbta-unit
ipaUserObjectClasses: top
ipaUserObjectClasses: person
ipaUserObjectClasses: organizationalperson
ipaUserObjectClasses: inetorgperson
ipaUserObjectClasses: inetuser
ipaUserObjectClasses: posixaccount
ipaUserObjectClasses: krbprincipalaux
ipaUserObjectClasses: krbticketpolicyaux
ipaUserObjectClasses: ipaobject
ipaUserObjectClasses: ipasshuser
ipaUserObjectClasses: x-ald-user
ipaUserObjectClasses: x-ald-user-parsec14
ipaUserObjectClasses: x-ald-audit-policy
ipaUserObjectClasses: rbta-unit
ipaUserObjectClasses: rbta-address
ipaUserObjectClasses: rbtaCustomUserAttrs
ipaUserObjectClasses: rbta-inetorgperson-ext
ipaUserObjectClasses: ruPostMailAccount
ipaUserObjectClasses: rbtaUserMeta
С помощью ipa config-mod можно добавить новый класс workplace и тогда он будет добавляться автоматически к новым записям пользователей при их создании. В команде необходимо перечислить все классы через запятую, добавив в конце новый класс:
dc-1:~# ipa config-mod --userobjectclasses={top,person,organizationalperson,inetorgperson,inetuser,posixaccount,krbprincipalaux,krbticketpolicyaux,ipaobject,ipasshuser,x-ald-user,x-ald-user-parsec14,x-ald-audit-policy,rbta-unit,rbta-address,rbtaCustomUserAttrs,rbta-inetorgperson-ext,ruPostMailAccount,rbtaUserMeta,workplace}
...
Классы объектов для пользователей по умолчанию: top, person, organizationalperson, inetorgperson, inetuser,
posixaccount, krbprincipalaux, krbticketpolicyaux,
ipaobject, ipasshuser, x-ald-user, x-ald-user-parsec14,
x-ald-audit-policy, rbta-unit, rbta-address,
rbtaCustomUserAttrs, rbta-inetorgperson-ext,
ruPostMailAccount, rbtaUserMeta, workplace
...
Аналогично для групп, если надо создать новый класс для групп, в команде вместо ключа --userobjectclasses используется ключ --groupobjectclasses.
Удобнее добавлять новые классы через Портал Управления ALD Pro через пункты меню Управление доменом – Пользователи и группы. Справа будет список существующих классов, слева список классов по умолчанию. См. Параметры пользователей. Классы.
Рисунок 70 Параметры пользователей. Классы¶
На следующей вкладе доступны аналогичные настройки для групп. См. Параметры групп. Классы.
Рисунок 71 Параметры групп. Классы¶
Присвоение значений пользовательским атрибутам¶
После того, как класс добавлен для записей пользователей, атрибуты idCabinet, idTable доступны для добавления и редактирования. Пользователь user2 был создан через Портал Управления ALD Pro. Присвойте значения атрибутам idCabinet, idTable для user2:
dc-1:~# ldapmodify -D "cn=Directory Manager" -W
Enter LDAP Password:
dn: uid=user2,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
changetype: modify
add: idCabinet
idCabinet: Отдел статистики
-
add: idTable
idTable: 10
modifying entry "uid=user1,cn=users,cn=accounts,dc=ald,dc=company,dc=lan"
Команда завершена успешно, для проверки можно обратить внимание на атрибуты пользователя user2:
dc-1:~# ldapsearch -Q -LLL -D "cn=Directory Manager" -W -b "uid=user2,cn=users,cn=accounts,dc=ald,dc=company,dc=lan" idCabinet idTable
Enter LDAP Password:
dn: uid=user1,cn=users,cn=accounts,dc=ald,dc=company,dc=lan
idCabinet:: 0J7RgtC00LXQuyDRgdGC0LDRgtC40YHRgtC40LrQuA==
idTable: 10
Так как IdCabinet содержит кириллицу, в выводе команды он отображается в base64-кодировке.
Заключение¶
LDAP разрабатывался с целью хранения любой информации об объектах на предприятии, таких как пользователи, компьютеры, серверы, подразделения и др. Информация удобно расположена в виде иерархического древа, которое строго типизировано объектными классами. База данных ориентирована на чтение, где быстро и легко можно получить любую информацию по объектам.
На сегодняшний день LDAP-каталог – это стандарт. Множество продуктов имеют встроенные интеграции с службой каталога, а также существует большое количество библиотек для разных языков для доступа к LDAP-серверу, например: python-ldap для python, ldaptive для java. В некоторые языки программирования уже встроена работа LDAP, например, в языки PHP и С#. А также у продукта ALD Pro есть REST API, через него можно управлять каталогом, используя простые Web-запросы.